package deadlock.avoidance;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Author: Holger(StackOverflow.com
 * A few efficient strategies to avoid deadlocks using the common scenario of bank acount transfer
 */
public class AvoidDeadLockDemo {
    /**
     * Notes: The crucial point here is correct arrangement of try finnaly block that confront
     * the best practise of using a Lock. Always put lock() in try and unlock() of the same lock
     * in the corresponding finnally block.
     */
    class Account {
        private int balance;
        Lock lock = new ReentrantLock();

        /**
         * Avoid deadlock by try the lock first,and abort partial resources
         * if the remainnning lock is not available.
         *
         * @param target The target account
         * @param amt    The amount of money to be transferred
         * @return if the transfer succeeds or not
         */
        boolean transfer(Account target, int amt) {
            boolean success = false;
            //lock the from account
            try {
                if (lock.tryLock(50, TimeUnit.MILLISECONDS)) try {
                    if (target.lock.tryLock(50, TimeUnit.MILLISECONDS)) try {
                        this.balance -= amt;
                        target.balance += amt;
                        success = true;
                    } finally {
                        target.lock.unlock();
                    }
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException ex) {
                // success still false
            }
            return success;
        }

    }

    /**
     * Just avoid deadlocks by always accessing the resources in a
     * predefinied order
     */
    class Account2 {
        private int balance;
        Lock lock = new ReentrantLock();
        int id;

        void transfer(Account2 target, int amt) {

            if (this.id > target.id) {
                transfer(this, target, amt);
            } else {
                transfer(target, this, -amt);
            }
        }

        void transfer(Account2 from, Account2 to, int amt) {
            boolean success = false;
            from.lock.lock();
            try {
                to.lock.lock();
                try {
                    from.balance -= amt;
                    to.balance += amt;

                } finally {
                    to.lock.unlock();
                }
            } finally {
                from.lock.unlock();
            }
        }
    }

}
